﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Preactor.ObjectModelExtensions
{
    using Preactor.ObjectModelExtensions.Entities;

    public static class PlanningBoardExtensions
    {
        public static void ClearPlanningBoard(this IPlanningBoard planningBoard)
        {
            DateTimeSpan dts = planningBoard.ScheduleHorizon;

            planningBoard.UnallocateAll(planningBoard.TerminatorTime, dts.End, OperationReferencePoint.ProcessEnd);
        }

        public static void MoveTerminator(this IPlanningBoard planningBoard, DateTime dateTime)
        {
            planningBoard.TerminatorTime = dateTime;
        }

        // NOTE: Deprecate this function and replace with something else.
        public static TimeSpan GetShiftPatternDuration(this IPlanningBoard planningBoard, string resourceName, DateTime startTime, DateTime endTime)
        {
            List<CalendarStateChange> shiftPatterns = planningBoard.GetCalendarStateChanges(resourceName, startTime, endTime);

            // Efficiency, Duration
            var dctEffDuration = new List<KeyValuePair<double, TimeSpan>>();

            for (int i = 1; i < shiftPatterns.Count; i++)
            {
                TimeSpan duration = shiftPatterns[i].ChangeTime - shiftPatterns[i - 1].ChangeTime;

                double efficiency = shiftPatterns[i - 1].CurrentState.Efficiency;

                if (efficiency > 0)
                {
                    efficiency = efficiency / 100;
                }

                // else efficiency is equal to 0
                dctEffDuration.Add(new KeyValuePair<double, TimeSpan>(efficiency, duration));
            }

            double totalDuration = dctEffDuration.Select(x => x.Key * x.Value.TotalSeconds).Sum();

            return TimeSpan.FromSeconds(totalDuration);
        }

        public static List<CalendarStateChange> GetCalendarStateChanges(this IPlanningBoard planningBoard, 
            string resourceName, 
            DateTime startDate, 
            DateTime endDate, 
            bool startFromBeginningOfFirstCalendarShift = false,
            bool finishAtEndofLastCalendarShift = false,
            bool includeNewDayCalendarState = false,
            TimeSpan startOfDay = default(TimeSpan)
            )
        {
            int resourceRecord = planningBoard.GetResourceNumber(resourceName);

            var calendarStateChanges = new List<CalendarStateChange>();

            if(startFromBeginningOfFirstCalendarShift)
            {
                startDate = GetStartOfCalendarState(planningBoard, startDate, resourceRecord, includeNewDayCalendarState, startOfDay);
            }

            if(finishAtEndofLastCalendarShift)
            {
                endDate = GetEndOfCalendarState(planningBoard, endDate, resourceRecord, includeNewDayCalendarState, startOfDay);
            }

            CalendarState? currentState = planningBoard.GetCurrentCalendarState(resourceRecord, startDate);

            if (currentState.HasValue)
            {
                var currentStateChange = new CalendarStateChange(startDate, currentState.Value);

                calendarStateChanges.Add(currentStateChange);

                CalendarStateChange? nextStateChange = planningBoard.GetNextCalendarState(resourceRecord, startDate);

                var previousState = currentStateChange;

                while (nextStateChange.HasValue && startDate < endDate)
                {
                    // Check whether the nextStateChange is in a new day
                    if (includeNewDayCalendarState)
                    {
                        var newDayDate = startDate.Date.AddDays( 1 ).Add( startOfDay );

                        if (nextStateChange.Value.ChangeTime > newDayDate)
                        {
                            var newDayStateChange = new CalendarStateChange(newDayDate, previousState.CurrentState);
                            calendarStateChanges.Add( newDayStateChange );
                        }
                    }

                    if (nextStateChange.Value.ChangeTime < endDate)
                    {
                        calendarStateChanges.Add(nextStateChange.Value);
                        previousState = nextStateChange.Value;
                    }

                    startDate = nextStateChange.Value.ChangeTime;
                    nextStateChange = planningBoard.GetNextCalendarState(resourceRecord, startDate);
                }

                // Check new day is less than endDate and greater than startDate
                if (includeNewDayCalendarState && startDate.Date.AddDays(1).Add(startOfDay) < endDate)
                {
                    var newDayStateChange = new CalendarStateChange(startDate.Date.AddDays(1).Add(startOfDay), previousState.CurrentState);
                    calendarStateChanges.Add(newDayStateChange);
                }

                CalendarState? endState = planningBoard.GetCurrentCalendarState(resourceRecord, endDate);

                if (endState.HasValue)
                {
                    CalendarStateChange? endStateChange = new CalendarStateChange(endDate, endState.Value);
                    calendarStateChanges.Add(endStateChange.Value);
                }
            }

            return calendarStateChanges;
        }

        public static DateTime GetStartOfCalendarState(this IPlanningBoard planningBoard, DateTime startDate, int resourceRecord, bool includeNewDayCalendarState, TimeSpan startOfDay)
        {
            var newStartDate = startDate;

            if (includeNewDayCalendarState && startDate.TimeOfDay != startOfDay)
            {
                return newStartDate;
            }

            CalendarState? currentState = planningBoard.GetCurrentCalendarState(resourceRecord, startDate);

            if (!currentState.HasValue)
            {
                return newStartDate;
            }

            CalendarStateChange? previousCalendarState = planningBoard.GetPreviousCalendarState( resourceRecord, startDate );

            if (previousCalendarState.HasValue)
            {
                newStartDate = previousCalendarState.Value.ChangeTime;

                // There is a chance the startDate actually is the start time of a new calendarStateChange.
                // Double check.
                CalendarStateChange? nextStartState = planningBoard.GetNextCalendarState( resourceRecord, newStartDate );

                if ( nextStartState.HasValue && nextStartState.Value.ChangeTime == startDate )
                {
                    newStartDate = startDate;
                }
            }

            return newStartDate;
        }

        public static DateTime GetEndOfCalendarState(this IPlanningBoard planningBoard, DateTime endDate, int resourceRecord, bool includeNewDayCalendarState, TimeSpan startOfDay)
        {
            var newEndDate = endDate;

            if (includeNewDayCalendarState && endDate.TimeOfDay != startOfDay)
            {
                return newEndDate;
            }

            CalendarStateChange? nextEndState = planningBoard.GetNextCalendarState(resourceRecord, endDate);

            if (!nextEndState.HasValue)
            {
                return newEndDate;
            }

            newEndDate = nextEndState.Value.ChangeTime;

            // There is a chance the endDate was actually the end of a calendar period.
            // Double check
            CalendarStateChange? previousCalendarState = planningBoard.GetPreviousCalendarState(resourceRecord, newEndDate);

            if (previousCalendarState.HasValue && previousCalendarState.Value.ChangeTime == endDate)
            {
                newEndDate = endDate;
            } 

            return newEndDate;
        }
    }
}